//--------------------------------------------------------------------------------
// This file is part of the Windows Workflow Foundation Sample Code
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// This source code is intended only as a supplement to Microsoft
// Development Tools and/or on-line documentation.  See these other
// materials for detailed information regarding Microsoft code samples.
// 
// THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//--------------------------------------------------------------------------------

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.Runtime;
using System.Security.Permissions;
using System.Security.Principal;
using System.Threading;
using System.Drawing.Drawing2D;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using Microsoft.Samples.Workflow.UI.Design;
using System.Drawing.Design;

namespace Microsoft.Samples.Workflow.UI
{
    [ActivityValidator(typeof(InteractionBaseActivityValidator))]
    public abstract class InteractionBaseActivity : CompositeActivity, IDynamicPropertyTypeProvider, IActivityEventListener<QueueEventArgs>
    {
        public static readonly DependencyProperty ReceiveInputProperty = DependencyProperty.Register("ReceiveInput", typeof(bool), typeof(InteractionBaseActivity), new PropertyMetadata(true, DependencyPropertyOptions.Metadata));
        public static readonly DependencyProperty ActivityInputTypeProperty = DependencyProperty.Register("ActivityInputType", typeof(Type), typeof(InteractionBaseActivity), new PropertyMetadata(null, DependencyPropertyOptions.Metadata, null));
        public static readonly DependencyProperty ActivityInputProperty = DependencyProperty.Register("ActivityInput", typeof(object), typeof(InteractionBaseActivity));
        public static readonly DependencyProperty ActivityOutputTypeProperty = DependencyProperty.Register("ActivityOutputType", typeof(Type), typeof(InteractionBaseActivity), new PropertyMetadata(null, DependencyPropertyOptions.Metadata, null));
        public static readonly DependencyProperty ActivityOutputProperty = DependencyProperty.Register("ActivityOutput", typeof(object), typeof(InteractionBaseActivity));
        public static readonly DependencyProperty RequiredPermissionProperty = DependencyProperty.Register("RequiredPermission", typeof(Permission), typeof(InteractionBaseActivity));
        public static readonly DependencyProperty ErrorProperty = DependencyProperty.Register("Error", typeof(Exception), typeof(InteractionBaseActivity));

        public static readonly DependencyProperty InitializedEvent = DependencyProperty.Register("Initialized", typeof(EventHandler<EventArgs>), typeof(InteractionBaseActivity));
        public static readonly DependencyProperty InputArrivedEvent = DependencyProperty.Register("InputArrived", typeof(EventHandler<InputArrivedEventArgs>), typeof(InteractionBaseActivity));

        internal static readonly DependencyProperty InteractionCommandProperty = DependencyProperty.RegisterAttached("InteractionCommand", typeof(InteractionCommand), typeof(InteractionBaseActivity), new PropertyMetadata(DependencyPropertyOptions.NonSerialized));
        static readonly DependencyProperty CancelledOrFaultedProperty = DependencyProperty.RegisterAttached("CancelledOrFaulted", typeof(bool), typeof(InteractionBaseActivity));
        static readonly DependencyProperty InteractionIdProperty = DependencyProperty.Register("InteractionId", typeof(Guid), typeof(InteractionBaseActivity));

        public InteractionBaseActivity()
        {
        }

        public InteractionBaseActivity(string name)
            : base(name)
        {
        }

        [SRCategory(SR.Interaction)]
        [SRDescription(SR.ReceiveInputDescription)]
        [MergableProperty(false)]
        [RefreshProperties(RefreshProperties.All)]
        public bool ReceiveInput
        {
            get
            {
                return (bool)base.GetValue(ReceiveInputProperty);
            }
            set
            {
                base.SetValue(ReceiveInputProperty, value);
            }
        }

        [SRCategory(SR.Interaction)]
        [SRDescription(SR.ActivityInputTypeDescription)]
        [MergableProperty(false)]
        [Editor(typeof(TypeBrowserEditor), typeof(UITypeEditor))]
        [ValidationOption(ValidationOption.Required)]
        public Type ActivityInputType
        {
            get
            {
                return base.GetValue(ActivityInputTypeProperty) as Type;
            }
            set
            {
                base.SetValue(ActivityInputTypeProperty, value);
            }
        }

        [SRCategory(SR.Interaction)]
        [SRDescription(SR.ActivityInputDescription)]
        [MergableProperty(false)]
        public object ActivityInput
        {
            get
            {
                return base.GetValue(ActivityInputProperty);
            }
            set
            {
                base.SetValue(ActivityInputProperty, value);
            }
        }

        [SRCategory(SR.Interaction)]
        [SRDescription(SR.ActivityOutputTypeDescription)]
        [MergableProperty(false)]
        [Editor(typeof(TypeBrowserEditor), typeof(UITypeEditor))]
        [ValidationOption(ValidationOption.Required)]
        [RefreshProperties(RefreshProperties.All)]
        public Type ActivityOutputType
        {
            get
            {
                return base.GetValue(ActivityOutputTypeProperty) as Type;
            }
            set
            {
                base.SetValue(ActivityOutputTypeProperty, value);
            }
        }

        [SRCategory(SR.Interaction)]
        [SRDescription(SR.ActivityOutputDescription)]
        [MergableProperty(false)]
        [TypeConverter(typeof(ActivityOutputValueTypeConverter))]
        public object ActivityOutput
        {
            get
            {
                return base.GetValue(ActivityOutputProperty);
            }
            set
            {
                base.SetValue(ActivityOutputProperty, value);
            }
        }

        [SRCategory(SR.Interaction)]
        [SRDescription(SR.RequiredPermissionDescription)]
        [MergableProperty(false)]
        public Permission RequiredPermission
        {
            get
            {
                return (Permission)base.GetValue(RequiredPermissionProperty);
            }
            set
            {
                base.SetValue(RequiredPermissionProperty, value);
            }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public Exception Error
        {
            get
            {
                return (Exception)base.GetValue(ErrorProperty);
            }
            set
            {
                base.SetValue(ErrorProperty, value);
            }
        }

        [SRDescription(SR.InitializedEventDescription)]
        [SRCategory(SR.Handlers)]
        [MergableProperty(false)]
        public event EventHandler<EventArgs> Initialized
        {
            add
            {
                base.AddHandler(InitializedEvent, value);
            }
            remove
            {
                base.RemoveHandler(InitializedEvent, value);
            }
        }

        [SRDescription(SR.InputArrivedEventDescription)]
        [SRCategory(SR.Handlers)]
        [MergableProperty(false)]
        public event EventHandler<InputArrivedEventArgs> InputArrived
        {
            add
            {
                base.AddHandler(InputArrivedEvent, value);
            }
            remove
            {
                base.RemoveHandler(InputArrivedEvent, value);
            }
        }

        [Browsable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        internal Guid InteractionId
        {
            get
            {
                return (Guid)base.GetValue(InteractionIdProperty);
            }
        }

        protected internal virtual InteractionContext GetInteractionContext()
        {
            object outputData = this.ActivityOutput;
            if (outputData is ICloneable)
            {
                outputData = ((ICloneable)outputData).Clone();
            }

            return new InteractionContext(new Bookmark(this.QualifiedName, this.InteractionId), outputData, this.RequiredPermission, this.Error);
        }

        protected internal virtual void InitializePermission(Permission ownerPermission)
        {
            if (this.RequiredPermission == null)
            {
                this.RequiredPermission = ownerPermission;
            }
        }

        protected override sealed void Initialize(IServiceProvider provider)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            base.Initialize(provider);
            OnInitialize(provider);

            if (this.IsDynamicActivity && this.ReceiveInput)
            {
                WorkflowQueuingService queueService = provider.GetService(typeof(WorkflowQueuingService)) as WorkflowQueuingService;
                if (!queueService.Exists(this.QualifiedName))
                {
                    queueService.CreateWorkflowQueue(this.QualifiedName, true);
                }
            }
        }

        protected virtual void OnInitialize(IServiceProvider provider)
        {
        }

        protected override sealed void Uninitialize(IServiceProvider provider)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (this.IsDynamicActivity && this.ReceiveInput)
            {
                // TODO: what to do with parallel replicator?  Right now, all instances listen to the same queue event.
                // If we delete the queue, the remaining instance will fail.
                WorkflowQueuingService queueService = provider.GetService(typeof(WorkflowQueuingService)) as WorkflowQueuingService;
                if (queueService.Exists(this.QualifiedName))
                {
                    queueService.DeleteWorkflowQueue(this.QualifiedName);
                }
            }

            base.Uninitialize(provider);
            OnUninitialize(provider);
        }

        protected virtual void OnUninitialize(IServiceProvider provider)
        {
        }

        protected sealed override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            if (executionContext == null)
            {
                throw new ArgumentNullException("executionContext");
            }

            if (executionContext == null)
            {
                throw new ArgumentNullException("executionContext");
            }

            if (this.InteractionId == Guid.Empty)
            {
                this.SetValue(InteractionIdProperty, Guid.NewGuid());
            }

            // First raise the Initialized event, which could be used by user to initialize the data object.
            this.RaiseGenericEvent<EventArgs>(InitializedEvent, this, new EventArgs());

            // First send the page for displaying.
            //DisplayUI(executionContext);
            Activity rootActivity = Helpers.GetRootActivity(this);
            InteractionContext currentInteraction = this.GetInteractionContext();
            InteractionCommand interactionCommand = rootActivity.GetValue(InteractionCommandProperty) as InteractionCommand;
            if (interactionCommand != null)
            {
                interactionCommand.NextInteraction = currentInteraction;
            }

            // Then wait for reply
            if (this.ReceiveInput)
            {
                // Not wait for input from the page.
                WorkflowQueuingService queueService = executionContext.GetService(typeof(WorkflowQueuingService)) as WorkflowQueuingService;
                WorkflowQueue queue = null;
                if (!queueService.Exists(this.QualifiedName))
                {
                    queue = queueService.CreateWorkflowQueue(this.QualifiedName, true);
                }
                else
                {
                    queue = queueService.GetWorkflowQueue(this.QualifiedName);
                }

                // Check to see if a message is already available.
                if (queue.Count > 0 && ProcessMessage(queue, this))
                {
                    return OnExecute(executionContext);
                }
                else
                {
                    // Subscribe to message arrive event.
                    queue.RegisterForQueueItemAvailable((IActivityEventListener<QueueEventArgs>)this);
                    return this.ExecutionStatus;
                }
            }
            else
                return OnExecute(executionContext);
        }

        protected virtual ActivityExecutionStatus OnExecute(ActivityExecutionContext executionContext)
        {
            return ActivityExecutionStatus.Closed;
        }

        protected sealed override ActivityExecutionStatus Cancel(ActivityExecutionContext executionContext)
        {
            if (executionContext == null)
            {
                throw new ArgumentNullException("executionContext");
            }

            this.CancelOrFaulted(executionContext);

            return OnCancel(executionContext);
        }

        protected virtual ActivityExecutionStatus OnCancel(ActivityExecutionContext executionContext)
        {
            return base.Cancel(executionContext);
        }

        protected override sealed ActivityExecutionStatus HandleFault(ActivityExecutionContext executionContext, Exception exception)
        {
            if (executionContext == null)
            {
                throw new ArgumentNullException("executionContext");
            }

            if (exception == null)
            {
                throw new ArgumentNullException("exception");
            }

            this.CancelOrFaulted(executionContext);

            return OnHandleFault(executionContext, exception);
        }

        protected virtual ActivityExecutionStatus OnHandleFault(ActivityExecutionContext executionContext, Exception exception)
        {
            return base.HandleFault(executionContext, exception);
        }

        protected sealed override void OnClosed(IServiceProvider provider)
        {
            if (provider == null)
            {
                throw new ArgumentNullException("provider");
            }

            if (this.ExecutionStatus == ActivityExecutionStatus.Closed &&
                !((bool)this.GetValue(CancelledOrFaultedProperty)))
            {
                this.RemoveProperty(CancelledOrFaultedProperty);
            }

            OnClose(provider);
        }

        protected virtual void OnClose(IServiceProvider provider)
        {
            base.OnClosed(provider);
        }

        void IActivityEventListener<QueueEventArgs>.OnEvent(object sender, QueueEventArgs e)
        {
            if (this.ProcessQueueEvent(sender, e))
            {
                ActivityExecutionContext context = (ActivityExecutionContext)sender;
                ActivityExecutionStatus newStatus = OnExecute(context);
                if (newStatus == ActivityExecutionStatus.Closed)
                {
                    context.CloseActivity();
                }
            }
        }

        Type IDynamicPropertyTypeProvider.GetPropertyType(IServiceProvider serviceProvider, string propertyName)
        {
            if (propertyName == null)
            {
                throw new ArgumentNullException("propertyName");
            }

            if (propertyName == "ActivityInput")
            {
                return this.ActivityInputType;
            }
            else if (propertyName == "ActivityOutput")
            {
                return this.ActivityOutputType;
            }

            return null;
        }

        AccessTypes IDynamicPropertyTypeProvider.GetAccessType(IServiceProvider serviceProvider, string propertyName)
        {
            return AccessTypes.Read;
        }

        bool ProcessMessage(WorkflowQueue queue, InteractionBaseActivity interactionActivity)
        {
            bool processed = true;
            ForwardCommand inputCmd = queue.Dequeue() as ForwardCommand;
            if (inputCmd == null)
            {
                interactionActivity.Error = new ArgumentException(SR.GetString(SR.Error_UnknownInputFormat, typeof(ForwardCommand).FullName), "inputCmd");
                processed = false;
            }
            else if (inputCmd.Input == null)
            {
                interactionActivity.Error = new ArgumentException(SR.GetString(SR.Error_UnknownInputFormat, typeof(UserInput).FullName), "inputCmd.Input");
                processed = false;
            }
            else
            {
                if (this.RequiredPermission != null)
                {
                    processed = this.RequiredPermission.CheckPermission();
                    if (!processed)
                    {
                        interactionActivity.Error = this.RequiredPermission.Exception;
                        inputCmd.NextInteraction = interactionActivity.GetInteractionContext();
                    }
                }

                if (processed)
                {
                    this.SetValue(ActivityInputProperty, inputCmd.Input.ActivityInput);
                    Activity rootActivity = Helpers.GetRootActivity(this);
                    rootActivity.SetValue(InteractionCommandProperty, inputCmd);
                    base.RaiseGenericEvent(InputArrivedEvent, this, new InputArrivedEventArgs(inputCmd.Input));
                }
            }

            return processed;
        }

        void CancelOrFaulted(ActivityExecutionContext executionContext)
        {
            WorkflowQueuingService queueService = executionContext.GetService(typeof(WorkflowQueuingService)) as WorkflowQueuingService;
            WorkflowQueue queue = queueService.GetWorkflowQueue(this.QualifiedName);
            queue.UnregisterForQueueItemArrived((IActivityEventListener<QueueEventArgs>)this);
            this.SetValue(CancelledOrFaultedProperty, true);
        }

        bool ProcessQueueEvent(object sender, QueueEventArgs e)
        {
            ActivityExecutionContext context = (ActivityExecutionContext)sender;
            InteractionBaseActivity activity = context.Activity as InteractionBaseActivity;
            if (activity == null)
            {
                throw new ArgumentException(SR.GetString(SR.Error_UnexpectedArgumentType, typeof(InteractionBaseActivity).FullName), "context.Activity");
            }

            // if activity is not scheduled for execution do not dequeue the message
            if (activity.ExecutionStatus != ActivityExecutionStatus.Executing)
            {
                return false;
            }

            WorkflowQueuingService queueService = context.GetService(typeof(WorkflowQueuingService)) as WorkflowQueuingService;
            WorkflowQueue queue = queueService.GetWorkflowQueue(activity.QualifiedName);
            if (queue.Count > 0)
            {
                return ProcessMessage(queue, activity);
            }
            else
            {
                return false;
            }
        }
    }
}
